////////////////////////////////////////////////////////////////////////////
//
//  Crytek Engine Source File.
//  Copyright (C), Crytek Studios, 2001.
// -------------------------------------------------------------------------
//  File name:   entitynode.cpp
//  Version:     v1.00
//  Created:     23/4/2002 by Timur.
//  Compilers:   Visual C++ 7.0
//  Description: 
// -------------------------------------------------------------------------
//  History:
//
////////////////////////////////////////////////////////////////////////////

#include "StdAfx.h"
#include "EntityNode.h"
#include "EventTrack.h"
#include "CharacterTrack.h"
#include "AnimSplineTrack.h"
#include "ExprTrack.h"
#include "BoolTrack.h"
#include "ISystem.h"
#include "SelectTrack.h"
#include "FaceSeqTrack.h"
#include "LookAtTrack.h"
#include "CompoundSplineTrack.h"
#include "Movie.h"
#include "PNoise3.h"

#include <ISound.h>
#include <ILipSync.h>
#include <ICryAnimation.h>
#include <CryCharMorphParams.h>
#include <Cry_Camera.h>

// AI
#include <IAgent.h>
#include "IAIObject.h"
#include "IAIActor.h"
#include "IGameFramework.h" 
#include "../CryAction/IActorSystem.h"
#define HEAD_BONE_NAME "Bip01 Head"

#define s_nodeParamsInitialized s_nodeParamsInitializedEnt
#define s_nodeParams s_nodeParamsEnt
#define AddSupportedParam AddSupportedParamEnt

static const float TIMEJUMPED_TRANSITION_TIME = 1.0f;
static const float EPSILON = 0.01f;
static float movie_physicalentity_animation_lerp;

static uint32 LayerToTrack(uint32 layer)
{
	if (layer == 0)
		return 0;
	if (layer > ADDITIVE_LAYERS_OFFSET)
	{
		assert(layer < ADDITIVE_LAYERS_OFFSET+3);
		return layer - ADDITIVE_LAYERS_OFFSET;
	}
	CryLog("Layer is not used by a Track in Trackview");
	return 0; 
}

//////////////////////////////////////////////////////////////////////////
namespace
{
	bool s_nodeParamsInitialized = false;
	std::vector<IAnimNode::SParamInfo> s_nodeParams;

	void AddSupportedParam( std::vector<IAnimNode::SParamInfo> &nodeParams,const char *sName,int paramId,EAnimValue valueType,int flags=0 )
	{
		IAnimNode::SParamInfo param;
		param.name = sName;
		param.paramId = paramId;
		param.valueType = valueType;
		param.flags = flags;
		nodeParams.push_back( param );
	}

	void NotifyEntityScript(const IEntity* pEntity,const char* funcName)
	{
		IScriptTable *pEntityScript = pEntity->GetScriptTable();
		if(pEntityScript && pEntityScript->HaveValue(funcName))
			Script::CallMethod(pEntityScript, funcName);
	}
};

//////////////////////////////////////////////////////////////////////////
CAnimEntityNode::CAnimEntityNode()
{
	m_EntityId = 0;
	m_entityGuid = 0;
	m_target=NULL;
	m_bWasTransRot = false;

	m_pos(0,0,0);
	m_scale(1,1,1);
	m_rotate.SetIdentity();

	m_visible = true;

	m_time = 0.0f;

	m_lastEntityKey = -1;

	m_lookAtTarget = "";
	m_lookAtEntityId = 0;
	m_lookAtLocalPlayer = false;
	m_allowAdditionalTransforms = true;
	m_boneSet = eLookAtKeyBoneSet_HeadEyes;

	m_entityGuidTarget = 0;
	m_EntityIdTarget = 0;
	m_entityGuidSource = 0;
	m_EntityIdSource = 0;

	m_boneLinkName = "";

	m_layerPlaysAnimation[0] = m_layerPlaysAnimation[1] = m_layerPlaysAnimation[2] = false;

	m_lastAnimationKeys[0][0] = -1;
	m_lastAnimationKeys[0][1] = -1;
	m_lastAnimationKeys[1][0] = -1;
	m_lastAnimationKeys[1][1] = -1;
	m_lastAnimationKeys[2][0] = -1;
	m_lastAnimationKeys[2][1] = -1;

	m_bTimeJumped[0] = m_bTimeJumped[1] = m_bTimeJumped[2] = false;
	m_jumpTime[0] = m_jumpTime[1] = m_jumpTime[2] = 0.0f;

	CAnimEntityNode::Initialize();
}

//////////////////////////////////////////////////////////////////////////
void CAnimEntityNode::Initialize()
{
	if (!s_nodeParamsInitialized)
	{
		s_nodeParamsInitialized = true;
		s_nodeParams.reserve(11);
		AddSupportedParam( s_nodeParams,"Position",APARAM_POS,AVALUE_VECTOR );
		AddSupportedParam( s_nodeParams,"Rotation",APARAM_ROT,AVALUE_QUAT );
		AddSupportedParam( s_nodeParams,"Scale",APARAM_SCL,AVALUE_VECTOR );
		AddSupportedParam( s_nodeParams,"Visibility",APARAM_VISIBLE,AVALUE_BOOL );
		AddSupportedParam( s_nodeParams,"Event",APARAM_EVENT,AVALUE_EVENT );
		AddSupportedParam( s_nodeParams,"Sound",APARAM_SOUND,AVALUE_SOUND,PARAM_MULTIPLE_TRACKS );
		AddSupportedParam( s_nodeParams,"Animation",APARAM_ANIMATION,AVALUE_CHARACTER,PARAM_MULTIPLE_TRACKS );
		AddSupportedParam( s_nodeParams,"Expression",APARAM_EXPRESSION,AVALUE_EXPRESSION,PARAM_MULTIPLE_TRACKS );
		AddSupportedParam( s_nodeParams,"Facial Sequence",APARAM_FACE_SEQUENCE,AVALUE_FACESEQ );
		AddSupportedParam( s_nodeParams,"LookAt",APARAM_LOOKAT,AVALUE_LOOKAT );
		AddSupportedParam( s_nodeParams,"Noise",APARAM_NOISE,AVALUE_VECTOR4 );

		REGISTER_CVAR(movie_physicalentity_animation_lerp, 0.85f, 0, "Lerp value for animation-driven physical entities");
	}
}

//////////////////////////////////////////////////////////////////////////
void CAnimEntityNode::CreateDefaultTracks()
{
	CreateTrack(APARAM_POS);
	CreateTrack(APARAM_ROT);
	CreateTrack(APARAM_EVENT);
};

//////////////////////////////////////////////////////////////////////////
CAnimEntityNode::~CAnimEntityNode()
{
	ReleaseSounds();
}

//////////////////////////////////////////////////////////////////////////
int CAnimEntityNode::GetParamCount() const
{
	return CAnimEntityNode::GetParamCountStatic();
}

//////////////////////////////////////////////////////////////////////////
bool CAnimEntityNode::GetParamInfo( int nIndex, SParamInfo &info ) const
{
	return CAnimEntityNode::GetParamInfoStatic(nIndex, info);
}

//////////////////////////////////////////////////////////////////////////
int CAnimEntityNode::GetParamCountStatic()
{
	return s_nodeParams.size();
}

//////////////////////////////////////////////////////////////////////////
bool CAnimEntityNode::GetParamInfoStatic( int nIndex, SParamInfo& info )
{
	if (nIndex >= 0 && nIndex < (int)s_nodeParams.size())
	{
		info = s_nodeParams[nIndex];
		return true;
	}
	return false;
}

//////////////////////////////////////////////////////////////////////////
bool CAnimEntityNode::GetParamInfoFromId( int paramId, SParamInfo &info ) const
{
	for (int i = 0; i < (int)s_nodeParams.size(); i++)
	{
		if (s_nodeParams[i].paramId == paramId)
		{
			info = s_nodeParams[i];
			return true;
		}
	}
	return false;
}

//////////////////////////////////////////////////////////////////////////
IEntity* CAnimEntityNode::GetEntity()
{
	if (!m_EntityId)
	{
		m_EntityId = gEnv->pEntitySystem->FindEntityByGuid(m_entityGuid);
	}
	return gEnv->pEntitySystem->GetEntity(m_EntityId);
}

//////////////////////////////////////////////////////////////////////////
void CAnimEntityNode::SetEntityGuid( const EntityGUID &guid )
{
	m_entityGuid = guid;
	m_EntityId = gEnv->pEntitySystem->FindEntityByGuid(m_entityGuid);
}


//////////////////////////////////////////////////////////////////////////
void CAnimEntityNode::SetEntityGuidTarget( const EntityGUID &guid )
{
	m_entityGuidTarget = guid;
	m_EntityIdTarget = gEnv->pEntitySystem->FindEntityByGuid(m_entityGuidTarget);
}


//////////////////////////////////////////////////////////////////////////
void CAnimEntityNode::SetEntityGuidSource( const EntityGUID &guid )
{
	m_entityGuidSource = guid;
	m_EntityIdSource = gEnv->pEntitySystem->FindEntityByGuid(m_entityGuidSource);
}

//////////////////////////////////////////////////////////////////////////
void CAnimEntityNode::StillUpdate()
{
	IEntity *pEntity = GetEntity();
	if (!pEntity)
		return;

	int paramCount = NumTracks();
	for (int paramIndex = 0; paramIndex < paramCount; paramIndex++)
	{
		int trackType = m_tracks[paramIndex].paramId;
		IAnimTrack *pTrack = m_tracks[paramIndex].track;

		switch (trackType)
		{
		case APARAM_LOOKAT:
			{
				SAnimContext ec;
				ec.time = m_time;

				CLookAtTrack *pSelTrack = (CLookAtTrack*)pTrack;
				AnimateLookAt( pSelTrack,ec );
			}
			break;
		}
	}
}

//////////////////////////////////////////////////////////////////////////
void CAnimEntityNode::Animate( SAnimContext &ec )
{
	IEntity *pEntity = GetEntity();
	if (!pEntity)
		return;

	if (!pEntity->GetProxy(ENTITY_PROXY_ENTITYNODE))
 		pEntity->CreateProxy(ENTITY_PROXY_ENTITYNODE);

	Vec3 pos = m_pos;
	Quat rotate = m_rotate;
	Vec3 scale = m_scale;
	Vec4 noiseParam(0.f, 0.f, 0.f, 0.f);

	bool bPosModified = false;
	bool bAnglesModified = false;
	bool bScaleModified = false;
	bool bApplyNoise = false;
	
	IAnimTrack *posTrack = NULL;
	IAnimTrack *rotTrack = NULL;
	IAnimTrack *sclTrack = NULL;

	if (!ec.bResetting)
	{
		StopExpressions();
	}

	PrecacheKeys(ec.time);

	int nAnimCharacterLayer = 0;

	int trackCount = NumTracks();
	for (int paramIndex = 0; paramIndex < trackCount; paramIndex++)
	{
		int trackType = m_tracks[paramIndex].paramId;
		IAnimTrack *pTrack = m_tracks[paramIndex].track;
		if(pTrack->GetNumKeys() == 0)
			continue;

		if(ec.trackMask && ((ec.trackMask & (1 << pTrack->GetType())) == 0))
			continue;

		switch (trackType)
		{
		case APARAM_POS:
			posTrack = pTrack;
			posTrack->GetValue( ec.time,pos );
			if (pos != pEntity->GetPos())
			{
				bPosModified = true;
			}
			break;
		case APARAM_ROT:
			rotTrack = pTrack;
			rotTrack->GetValue( ec.time,rotate );
			if (rotate != pEntity->GetRotation())
				bAnglesModified = true;
			break;
		case APARAM_NOISE:
			static_cast<CCompoundSplineTrack*>(pTrack)->GetValue( ec.time, noiseParam );
			m_posNoise.amp = noiseParam.x;
			m_posNoise.freq = noiseParam.y;
			m_rotNoise.amp = noiseParam.z;
			m_rotNoise.freq = noiseParam.w;
			bApplyNoise = true;
			break;
		case APARAM_SCL:
			sclTrack = pTrack;
			sclTrack->GetValue( ec.time,scale );
			// Check whether the scale value is valid.
			if (scale.x < 0.01 || scale.y < 0.01 || scale.z < 0.01)
			{
				CryWarning(VALIDATOR_MODULE_MOVIE, VALIDATOR_WARNING, 
							"An EntityNode <%s> gets an invalid scale (%f,%f,%f) from a TrackView track, so ignored.",
							(const char*)GetName(), scale.x, scale.y, scale.z);
				scale = m_scale;
			}
			if (scale != pEntity->GetScale())
				bScaleModified = true;
			break;
		case APARAM_EVENT:
			if (!ec.bResetting)
			{
				CEventTrack *entityTrack = (CEventTrack*)pTrack;
				IEventKey key;
				int entityKey = entityTrack->GetActiveKey(ec.time,&key);
				// If key is different or if time is standing exactly on key time.
				//if ((entityKey != m_lastEntityKey || key.time == ec.time) && (!ec.bSingleFrame))
				if (entityKey != m_lastEntityKey || key.time == ec.time)
				{
					m_lastEntityKey = entityKey;
					if (entityKey >= 0)
					{
						bool bNotTrigger = key.bNoTriggerInScrubbing && ec.bSingleFrame && key.time != ec.time;
						if (!bNotTrigger)
						{
							bool bRagollizeEvent = strcmp(key.event, "Ragdollize") == 0;
							if (!bRagollizeEvent || GetCMovieSystem()->IsPhysicsEventsEnabled())
								ApplyEventKey( entityTrack,entityKey,key );
							bool bHideOrUnHide = strcmp(key.event, "Hide") == 0
																	|| strcmp(key.event, "UnHide") == 0;
							if (m_pOwner && bHideOrUnHide)
								m_pOwner->OnNodeHidden(this);
						}
					}
				}
			}
			break;
		case APARAM_VISIBLE:
			if (!ec.bResetting)
			{
				IAnimTrack *visTrack = pTrack;
				bool visible = m_visible;
				visTrack->GetValue( ec.time,visible );
				pEntity->Hide(!visible);
				m_visible = visible;
				if (m_pOwner)
					m_pOwner->OnNodeHidden(this);
			}
			break;

		//////////////////////////////////////////////////////////////////////////
		case APARAM_SOUND:
			if (!ec.bResetting)
			{
				CSoundTrack* const pSoundTrack = static_cast<CSoundTrack*>(pTrack);
				ISoundKey oSoundKey;
				ISoundKey oNextSoundKey;

				int nSoundKey           = pSoundTrack->GetActiveKey(ec.time, &oSoundKey);
				int const nNextSoundKey = nSoundKey + 1;
				int const nSoundIndex   = paramIndex;

				if (nSoundIndex >= (int)m_SoundInfo.size())
				{
					m_SoundInfo.resize(trackCount);
				}

				// Let's peek a second ahead.
				if (m_SoundInfo[nSoundIndex].nLastPrecachedKey != nNextSoundKey && pSoundTrack->GetNumKeys() > nNextSoundKey)
				{
					pSoundTrack->GetKey(nNextSoundKey, &oNextSoundKey);

					if (oNextSoundKey.bVoice == true && (oNextSoundKey.time - ec.time) < 1.0f)
					{
						if (oNextSoundKey.pszFilename && oNextSoundKey.pszFilename[0])
						{
							gEnv->pSoundSystem->PrecacheDialogLine(oNextSoundKey.pszFilename);
							m_SoundInfo[nSoundIndex].nLastPrecachedKey = nNextSoundKey;
						}
					}
				}

				if (!oSoundKey.bLoop && oSoundKey.time+(oSoundKey.fEndTime-oSoundKey.fStartTime)+EPSILON < ec.time)
 					nSoundKey = -1;

				// update
				_smart_ptr<ISound> pSound;

				if (m_SoundInfo[nSoundIndex].nSoundID != INVALID_SOUNDID)
					pSound = gEnv->pSoundSystem->GetSound(m_SoundInfo[nSoundIndex].nSoundID);

				bool const bStartSound = (nSoundKey != -1 && m_SoundInfo[nSoundIndex].nSoundID == INVALID_SOUNDID);

				if (nSoundKey != m_SoundInfo[nSoundIndex].nLastKey || oSoundKey.time == ec.time || bStartSound  || ( ec.bSingleFrame && !ec.bForcePlay ) )
				{
					m_SoundInfo[nSoundIndex].nLastKey = nSoundKey;
					ApplySoundKey( pSoundTrack,nSoundKey,nSoundIndex, oSoundKey, ec);
				}
				else
				{
					if (pSound)
					{
						SSoundInfo &sndInfo = m_SoundInfo[nSoundIndex];

						if( sndInfo.bLoaded == false )
						{
							// GetLengthMs() failed at the creation time, so let's try to get it again.
							int newLength = pSound->GetLengthMs();
							if( newLength > 0)
							{
								int oldLength = oSoundKey.nSoundLength;

								bool bNeedToExtend = oldLength < newLength && fabs(oSoundKey.fEndTime-(oldLength/1000.0f)) < EPSILON;
								bool bNeedToShrink = oldLength > newLength && (int)(oSoundKey.fEndTime * 1000.0f) > newLength;

								if( bNeedToExtend || bNeedToShrink )
									oSoundKey.fEndTime = ((float)newLength)/1000.0f;

								oSoundKey.nSoundLength = newLength;
								pTrack->SetKey( nSoundKey,&oSoundKey ); // Update key duration.
								sndInfo.bLoaded = true;
							}
						}

						if (pSound->GetFlags() & FLAG_SOUND_3D)
						{
							Vec3 SoundPos = pEntity->GetPos();
							Vec3 SoundDir = pEntity->GetWorldTM().TransformVector(FORWARD_DIRECTION);

							Vec3 vOffset(0);
							bool bVoice = (pSound->GetFlags() & FLAG_SOUND_VOICE) != 0;
							vOffset = Adjust3DSoundOffset(bVoice, pEntity, SoundPos);

							IEntitySoundProxy *pSoundProxy = (IEntitySoundProxy*)pEntity->CreateProxy(ENTITY_PROXY_SOUND);

							if (pSoundProxy)
							{
								pSoundProxy->UpdateSounds();

								pSoundProxy->SetSoundPos(pSound->GetId(), vOffset);
							}
							else
							{
								// also set sound position, because entity might not move at all
								pSound->SetPosition( SoundPos );
								pSound->SetDirection( SoundDir );
							}
						}
					}
				}

				// TODO: Review this, currently this seems of no use.
				// It's just stopping sounds forcefully at the end of their set duration.....if the current sound key is not looped??
				// That's not desired especially if sounds are a bit delayed.
				// The sequence designer should have this in mind when designing sequences,
				// meaning they should keep a small gap between sound events.
				/*if( pSound )
				{
					if( oSoundKey.bLoop == false )
					{
						if( ec.time - oSoundKey.time >= oSoundKey.fEndTime - oSoundKey.fStartTime )
							pSound->Stop();
					}
				}*/
			}
			break;
		
		//////////////////////////////////////////////////////////////////////////
		case APARAM_ANIMATION:
			if (!ec.bResetting)
			{
				if (nAnimCharacterLayer < MAX_CHARACTER_TRACKS + ADDITIVE_LAYERS_OFFSET)
				{
					int index = nAnimCharacterLayer;
					CCharacterTrack *pCharTrack = (CCharacterTrack*)pTrack;
					AnimateCharacterTrack( pCharTrack,ec,index );
					if (nAnimCharacterLayer == 0)
						nAnimCharacterLayer += ADDITIVE_LAYERS_OFFSET;
					nAnimCharacterLayer++;
				}
			}
			break;

		//////////////////////////////////////////////////////////////////////////
		case APARAM_EXPRESSION:
			if (!ec.bResetting)
			{
				CExprTrack *pExpTrack = (CExprTrack*)pTrack;
				AnimateExpressionTrack( pExpTrack,ec );
			}
			break;

			//////////////////////////////////////////////////////////////////////////
		case APARAM_FACE_SEQUENCE:
			if (!ec.bResetting)
			{
				CFaceSeqTrack *pSelTrack = (CFaceSeqTrack*)pTrack;
				AnimateFacialSequence( pSelTrack,ec );
			}
			break;
		
		case APARAM_LOOKAT:
			if (!ec.bResetting)
			{
				CLookAtTrack *pSelTrack = (CLookAtTrack*)pTrack;
				AnimateLookAt( pSelTrack,ec );
			}
			break;
		};
	}
	
	if (bApplyNoise)
	{
		// Position noise
		if (m_posNoise.amp != 0)
		{
			pos += m_posNoise.Get(ec.time);
			if (pos != m_pos)
			{
				bPosModified = true;
			}
		}

		// Rotation noise
		if (m_rotNoise.amp != 0)
		{
			Ang3 angles = Ang3::GetAnglesXYZ(Matrix33(rotate))*180.0f/gf_PI;
			Vec3 noiseVec = m_rotNoise.Get(ec.time);
			angles.x += noiseVec.x;
			angles.y += noiseVec.y;
			angles.z += noiseVec.z;
			rotate.SetRotationXYZ(angles*gf_PI/180.0f);

			if (rotate != m_rotate)
			{
				bAnglesModified = true;
			}
		}
	}

	m_time = ec.time;

	// [*DavidR | 6/Oct/2010] Positioning an entity when ragdollized will not look good at all :)
	// Note: Articulated != ragdoll in some cases. And kinematic(mass 0) articulated entities could allow
	// repositioning, but positioning a kinematic articulated entity by TrackView isn't something we expect 
	// to happen very much compared with regular ragdoll
	const bool bRagdoll = pEntity->GetPhysics() && (pEntity->GetPhysics()->GetType() == PE_ARTICULATED);
	if (!bRagdoll && (bPosModified || bAnglesModified || bScaleModified || (m_target!=NULL)))
	{
		m_pos = pos;
		m_rotate = rotate;
		m_scale = scale;

		m_bIgnoreSetParam = true; // Prevents feedback change of track.
		if (m_pOwner)
		{
			if (IsBoneLinkTransformEnabled())
			{
				if (bPosModified)
					UpdateBoneLink(&m_pos, NULL);
				if (bAnglesModified)
					UpdateBoneLink(NULL, &m_rotate);
			}
			m_pOwner->OnNodeAnimated(this);
		}
		else	// no callback specified, so lets move the entity directly
		{
			if (bPosModified && bAnglesModified)
			{
				if (IsBoneLinkTransformEnabled() == false)
				{
					const bool initialState = (ec.time == 0.f);
					UpdateEntityPositionAndRotation(pEntity, m_pos, m_rotate, initialState, EUEF_POSITION|EUEF_ROTATION);
					pEntity->SetScale( bScaleModified?m_scale:pEntity->GetScale(),ENTITY_XFORM_TRACKVIEW );
					bScaleModified=false;
				}
				else
				{
					UpdateBoneLink(&m_pos, &m_rotate);
				}
			}
			else
			{
				if (bPosModified)
				{
					if (IsBoneLinkTransformEnabled() == false)
						UpdateEntityPositionAndRotation(pEntity, m_pos, m_rotate, ec.time == 0.f, EUEF_POSITION);
					else
						UpdateBoneLink(&m_pos, NULL);
				}

				if (bAnglesModified)
				{
					if (IsBoneLinkTransformEnabled() == false)
						UpdateEntityPositionAndRotation( pEntity, m_pos, m_rotate, ec.time == 0.f, EUEF_ROTATION);
					else
						UpdateBoneLink(NULL, &m_rotate);
				}
			}

			if (bScaleModified)
				pEntity->SetScale(m_scale,ENTITY_XFORM_TRACKVIEW);
		}
		m_bIgnoreSetParam = false; // Prevents feedback change of track.
	}

	if(m_entityGuidTarget)
	{
		IEntity * pEntityTarget = 0;
		if (!m_EntityIdTarget)
			m_EntityIdTarget = gEnv->pEntitySystem->FindEntityByGuid(m_entityGuidTarget);
		if(m_EntityIdTarget)
			pEntityTarget = gEnv->pEntitySystem->GetEntity(m_EntityIdTarget);
		if(pEntityTarget)
		{
			Vec3 worldPos = m_pos;
			if(pEntity->GetParent())
			{
				worldPos = pEntity->GetParent()->GetWorldTM().TransformPoint(worldPos);
			}
			Matrix34 tm = Matrix34( Matrix33::CreateRotationVDir((pEntityTarget->GetWorldPos()-worldPos).GetNormalized()), worldPos );
			pEntity->SetWorldTM(tm, ENTITY_XFORM_TRACKVIEW);
		}
	}

	if(m_entityGuidSource)
	{
		IEntity * pEntitySource = 0;
		if (!m_EntityIdSource)
			m_EntityIdSource = gEnv->pEntitySystem->FindEntityByGuid(m_entityGuidSource);
		if(m_EntityIdSource)
			pEntitySource = gEnv->pEntitySystem->GetEntity(m_EntityIdSource);
		if(pEntitySource)
		{
			Matrix34 wtm = pEntitySource->GetWorldTM();
			Vec3 worldPos = wtm.GetTranslation();
			Matrix34 tm = Matrix34( Matrix33::CreateRotationVDir((pEntity->GetWorldPos()-worldPos).GetNormalized()), worldPos );
			pEntitySource->SetWorldTM(tm, ENTITY_XFORM_TRACKVIEW);
		}
	}
}

//////////////////////////////////////////////////////////////////////////
void CAnimEntityNode::ReleaseSounds()
{
	// stop all sounds
	_smart_ptr<ISound> pSound;

	for (int i=0,num = m_SoundInfo.size();i<num;i++)
	{
		SSoundInfo &sndInfo = m_SoundInfo[i];

		if (sndInfo.nSoundID != INVALID_SOUNDID)
			pSound = gEnv->pSoundSystem->GetSound(sndInfo.nSoundID);

		if (pSound)
			pSound->Stop();

		sndInfo.nSoundID = INVALID_SOUNDID;
		sndInfo.nLastKey = -1;
		
		if (!sndInfo.sLastFilename.empty())
			sndInfo.sLastFilename = "";
	}
}

//////////////////////////////////////////////////////////////////////////
void CAnimEntityNode::Reset()
{
	m_EntityId = 0;
	m_lastEntityKey = -1;
	m_lastAnimationKeys[0][0] = -1;
	m_lastAnimationKeys[0][1] = -1;
	m_lastAnimationKeys[1][0] = -1;
	m_lastAnimationKeys[1][1] = -1;
	m_lastAnimationKeys[2][0] = -1;
	m_lastAnimationKeys[2][1] = -1;
	m_lookAtTarget = "";
	m_lookAtEntityId = 0;
	m_allowAdditionalTransforms = true;
	m_boneSet = eLookAtKeyBoneSet_HeadEyes;

	ReleaseSounds();
	ReleaseAllAnims();

	m_layerPlaysAnimation[0] = m_layerPlaysAnimation[1] = m_layerPlaysAnimation[2] = false;
}

//////////////////////////////////////////////////////////////////////////
void CAnimEntityNode::ResetHard()
{
	Reset();
	if (m_pOwner)
		m_pOwner->OnNodeReset(this);
}

//////////////////////////////////////////////////////////////////////////
void CAnimEntityNode::PrepareAnimations()
{
	// Update durations of all character animations.
	IEntity *pEntity = GetEntity();
	if (!pEntity)
		return;
	ICharacterInstance *pCharacter = pEntity->GetCharacter(0);
	if (!pCharacter)
		return;

	IAnimationSet* pAnimations = pCharacter->GetIAnimationSet();
	if (!pAnimations)
		return;

	int trackCount = NumTracks();
	for (int paramIndex = 0; paramIndex < trackCount; paramIndex++)
	{
		int trackType = m_tracks[paramIndex].paramId;
		IAnimTrack *pTrack = m_tracks[paramIndex].track;

		switch (trackType)
		{
		case APARAM_ANIMATION:
			{
				int numKeys = pTrack->GetNumKeys();
				for (int i = 0; i < numKeys; i++)
				{
					ICharacterKey key;
					pTrack->GetKey( i,&key );
					int animId = pAnimations->GetAnimIDByName( key.m_animation );
					if (animId >= 0)
					{
						//float duration = pAnimations->GetLength(animId);
						float duration = pAnimations->GetDuration_sec(animId);
						if (duration != key.m_duration)
						{
							key.m_duration = duration;
							pTrack->SetKey( i,&key );
						}
					}
				}
			}
			break;
		case APARAM_FACE_SEQUENCE:
			{
				IFacialInstance *pFaceInstance = pCharacter->GetFacialInstance();
				if (pFaceInstance)
				{
					ISelectKey key;
					int numKeys = pTrack->GetNumKeys();
					for (int i = 0; i < numKeys; i++)
					{
						pTrack->GetKey( i,&key );
						_smart_ptr<IFacialAnimSequence> pSequence = LoadFacialSequence(key.szSelection);
						if (pSequence)
						{
							key.fDuration = pSequence->GetTimeRange().Length();
							pTrack->SetKey( i,&key );
						}
					}
				}
			}
			break;
		}
	}
}

//////////////////////////////////////////////////////////////////////////
IFacialAnimSequence* CAnimEntityNode::LoadFacialSequence(const char* sequenceName)
{
	FacialSequenceMap::iterator loadedSequencePosition = m_facialSequences.find(CONST_TEMP_STRING(sequenceName));
	if (loadedSequencePosition == m_facialSequences.end())
	{
		IEntity* pEntity = GetEntity();
		ICharacterInstance* pCharacter = (pEntity ? pEntity->GetCharacter(0) : 0);
		IFacialInstance* pFaceInstance = (pCharacter ? pCharacter->GetFacialInstance() : 0);
		IFacialAnimSequence* pSequence = (pFaceInstance ? pFaceInstance->LoadSequence( sequenceName, false ) : 0);

		// Add the sequence to the cache, even if the pointer is 0 - stop us from continually trying to load a missing sequence.
		loadedSequencePosition = m_facialSequences.insert(std::make_pair(sequenceName, pSequence)).first;
	}

	return (loadedSequencePosition != m_facialSequences.end() ? 
								(*loadedSequencePosition).second : 
								_smart_ptr<IFacialAnimSequence>(0));
}

//////////////////////////////////////////////////////////////////////////
void CAnimEntityNode::ReleaseAllFacialSequences()
{
	IEntity* pEntity = GetEntity();
	ICharacterInstance* pCharacter = (pEntity ? pEntity->GetCharacter(0) : 0);
	IFacialInstance* pFaceInstance = (pCharacter ? pCharacter->GetFacialInstance() : 0);
	if (pFaceInstance)
		pFaceInstance->StopSequence(eFacialSequenceLayer_Trackview);

	// If commented out facial sequence will not be released for this level and will stay cached
	//m_facialSequences.clear();
}


//////////////////////////////////////////////////////////////////////////
void CAnimEntityNode::Activate( bool bActivate )
{
	CAnimNode::Activate(bActivate);
	if (bActivate)
	{
		PrepareAnimations();
	}
	else
	{
		// reset.
		// Reset called explicitly by sequence.

		// Release lock on preloaded Animations so no locks can leak
		m_animationCacher.ClearCache();
	}

	m_lookAtTarget = "";
	m_lookAtEntityId = 0;
	m_allowAdditionalTransforms = true;
	m_boneSet = eLookAtKeyBoneSet_HeadEyes;
};

//////////////////////////////////////////////////////////////////////////
void CAnimEntityNode::Pause()
{
	ReleaseSounds();
}

//////////////////////////////////////////////////////////////////////////
void CAnimEntityNode::UpdateEntityPositionAndRotation( IEntity* piEntity, const Vec3& targetPos, const Quat& targetRot, const bool initialState, const TUpdateEntityFlags flags )
{
	IPhysicalEntity* pPhysEnt = (initialState || piEntity->GetParent()) ? NULL : piEntity->GetPhysics();
	if( pPhysEnt && pPhysEnt->GetType() != PE_STATIC )
	{
		pe_status_pos psp;
		pPhysEnt->GetStatus(&psp);
		const float frameTime = gEnv->pTimer->GetFrameTime();
		const float rTimeStep = 1.f / max(frameTime, 100e-3f);

		pe_action_set_velocity setVel;

		if(flags & EUEF_POSITION)
		{
			setVel.v = (targetPos - psp.pos) * (rTimeStep * movie_physicalentity_animation_lerp);
		}
		if(flags & EUEF_ROTATION)
		{
			const Quat dq = targetRot * psp.q.GetInverted();
			setVel.w = dq.v * (2.f * rTimeStep * movie_physicalentity_animation_lerp); //This is an approximation
		}

		pPhysEnt->Action( &setVel );
	}
	else
	{
		if(flags & EUEF_POSITION)
		{
			piEntity->SetPos( targetPos, ENTITY_XFORM_TRACKVIEW );
		}
		if(flags & EUEF_ROTATION)
		{
			piEntity->SetRotation( targetRot, ENTITY_XFORM_TRACKVIEW );
		}
	}
}


//////////////////////////////////////////////////////////////////////////
void CAnimEntityNode::SetPos( float time,const Vec3 &pos )
{
	bool bDefault = !(gEnv->pMovieSystem->IsRecording() && (m_flags&ANODE_FLAG_SELECTED)); // Only selected nodes can be recorded

	{
		IAnimTrack *posTrack = GetTrackForParameter(APARAM_POS);
		if (posTrack)
			posTrack->SetValue( time,pos,bDefault );
	}

	if (!bDefault)
		GetCMovieSystem()->Callback( IMovieCallback::CBR_CHANGETRACK ,this );

	m_pos = pos;
}
	
//////////////////////////////////////////////////////////////////////////
void CAnimEntityNode::SetRotate( float time,const Quat &quat )
{
	m_rotate = quat;

	bool bDefault = !(gEnv->pMovieSystem->IsRecording() && (m_flags&ANODE_FLAG_SELECTED)); // Only selected nodes can be recorded

	IAnimTrack *rotTrack = GetTrackForParameter(APARAM_ROT);
	if (rotTrack)
		rotTrack->SetValue( time,m_rotate,bDefault );

	if (!bDefault)
		GetCMovieSystem()->Callback( IMovieCallback::CBR_CHANGETRACK ,this );
}

//////////////////////////////////////////////////////////////////////////
void CAnimEntityNode::SetScale( float time,const Vec3 &scale )
{
	m_scale = scale;
	bool bDefault = !(gEnv->pMovieSystem->IsRecording() && (m_flags&ANODE_FLAG_SELECTED)); // Only selected nodes can be recorded

	IAnimTrack *sclTrack = GetTrackForParameter(APARAM_SCL);
	if (sclTrack)
		sclTrack->SetValue( time,scale,bDefault );

	if (!bDefault)
		GetCMovieSystem()->Callback( IMovieCallback::CBR_CHANGETRACK ,this );
}

//////////////////////////////////////////////////////////////////////////
void CAnimEntityNode::ApplyEventKey( CEventTrack *track,int keyIndex,IEventKey &key )
{
	IEntity* pEntity = GetEntity();
	if (!pEntity)
		return;

	if (*key.animation) // if there is an animation
	{
		// Start playing animation.
		ICharacterInstance *pCharacter = pEntity->GetCharacter(0);
		if (pCharacter)
		{
			CryCharAnimationParams aparams;
			//aparams.fBlendInTime = 0.5f;
			//aparams.fBlendOutTime = 0.5f;
			aparams.m_fTransTime  = 0.15f;

			aparams.m_fTransTime    = 0.0f;
			aparams.m_nFlags = CA_TRACK_VIEW_EXCLUSIVE;

			ISkeletonAnim* pISkeletonAnim = pCharacter->GetISkeletonAnim();
			
			aparams.m_nLayerID = 0;
			pISkeletonAnim->StartAnimation( key.animation,  aparams );
		//	aparams.m_nLayerID = 1;
		//	pISkeleton->StartAnimation( key.animation,0, 0,0, aparams );
		//	aparams.m_nLayerID = 2;
		//	pISkeleton->StartAnimation( key.animation,0, 0,0, aparams );

			IAnimationSet* pAnimations = pCharacter->GetIAnimationSet();
			assert (pAnimations);
			//float duration = pAnimations->GetLength( key.animation );

			int animId = pAnimations->GetAnimIDByName( key.animation );
			if (animId >= 0)
			{
				float duration = pAnimations->GetDuration_sec(animId);
				if (duration != key.duration)
				{
					key.duration = duration;
					track->SetKey( keyIndex,&key );
				}
			}
		}
		//char str[1024];
		//sprintf( str,"StartAnim: %s",key.animation );
		//CryLog( str );
	}
	
	if (*key.event) // if there's an event
	{
		// Fire event on Entity.
		IEntityScriptProxy *pScriptProxy = (IEntityScriptProxy*)pEntity->GetProxy(ENTITY_PROXY_SCRIPT);
		if (pScriptProxy)
		{
			// Find event
			int type = -1;
			if (IEntityClass *pClass = pEntity->GetClass())
			{
				int count = pClass->GetEventCount();
				IEntityClass::SEventInfo info;
				for (int i = 0; i < count; ++i)
				{
					info = pClass->GetEventInfo(i);
					if (strcmp(key.event,info.name) == 0)
					{
						type = info.type; 
						break;
					}
				}
			}

			// Convert value to type
			switch (type)
			{
				case IEntityClass::EVT_INT:
				case IEntityClass::EVT_FLOAT:
				case IEntityClass::EVT_ENTITY:
					pScriptProxy->CallEvent(key.event, (float)atof(key.eventValue));
					break;
				case IEntityClass::EVT_BOOL:
					pScriptProxy->CallEvent(key.event, atoi(key.eventValue)!=0?true:false);
					break;
				case IEntityClass::EVT_STRING:
					pScriptProxy->CallEvent(key.event, key.eventValue);
					break;
				case IEntityClass::EVT_VECTOR:
					{
						Vec3 vTemp(0,0,0);
						float x=0,y=0,z=0;
						int res = sscanf(key.eventValue,"%f,%f,%f",&x,&y,&z);
						assert(res==3);
						vTemp(x,y,z);
						pScriptProxy->CallEvent(key.event, vTemp);
					}
					break;
				case -1:
				default:
					pScriptProxy->CallEvent(key.event);
			}
			
		}
	}
	//m_entity->Hide( key.hidden );
}

//////////////////////////////////////////////////////////////////////////
void CAnimEntityNode::ApplySoundKey( IAnimTrack *pTrack,int nCurrKey,int nLayer, ISoundKey &key, SAnimContext &ec )
{
	IEntity* const pEntity = GetEntity();

	if (gEnv->pSoundSystem != NULL && pEntity != NULL)
	{
		SSoundInfo &sndInfo = m_SoundInfo[nLayer];

		_smart_ptr<ISound> pSound;

		if (sndInfo.nSoundID != INVALID_SOUNDID)
		{
			pSound = gEnv->pSoundSystem->GetSound(sndInfo.nSoundID);

			if (pSound && ((pSound->GetFlags() & FLAG_SOUND_LOOP) != 0 || (pSound->GetFlags() & FLAG_SOUND_VOICE) == 0))
			{
				// Only stop loops or non-voice sounds.
				pSound->Stop();
			}

			pSound = NULL;

			// Make sure to always reset the sound ID!
			sndInfo.nSoundID = INVALID_SOUNDID;
			sndInfo.bLoaded  = false;
		}

		// Only keep processing when we have a valid sound key ID.
		if (nCurrKey != -1)
		{
			int flags = 0;

			bool bNewSound = false;

			if (((strcmp(sndInfo.sLastFilename.c_str(), key.pszFilename) != 0) || (!pSound)))
			{
				if (key.bLoop)
					flags |= FLAG_SOUND_LOOP;

				if (key.bVoice)
					flags |= FLAG_SOUND_VOICE;

				if (key.b3DSound)
				{
					// 3D sound.
					flags |= FLAG_SOUND_3D;
				}
				else
				{
					// 2D sound.
					flags |= FLAG_SOUND_2D|FLAG_SOUND_STEREO|FLAG_SOUND_16BITS;
				} 

				// only try to create a sound if a sound is referenced. Keys to stop loopings sounds are empty intentionally
				if (strlen(key.pszFilename) > 0)
				{
					pSound = gEnv->pSoundSystem->CreateSound(key.pszFilename, flags | FLAG_SOUND_MOVIE);

					if (pSound)
					{
						sndInfo.nSoundID = pSound->GetId();

						if (key.bVoice)
						{
							const char *sDialogKey = gEnv->pSoundSystem->GetInterfaceExtended()->MakeDialogKey(key.pszFilename);
							strncpy(key.pszFilename, sDialogKey, sizeof(key.pszFilename));
							pSound->SetSemantic((ESoundSemantic)(eSoundSemantic_TrackView|eSoundSemantic_Dialog));
						}
						else
							pSound->SetSemantic(eSoundSemantic_TrackView);

						sndInfo.bLoaded = pSound->GetLengthMs() > 0;
					}
					else
					{
						if (!gEnv->pSoundSystem->GetInterfaceExtended()->IsNullImplementation())
							gEnv->pSoundSystem->Log(eSLT_Warning, "<Sound> In Trackview EntityNode <%s> sound <%s> can not be loaded.", GetName(), key.pszFilename);
					}

					sndInfo.sLastFilename = key.pszFilename;
					bNewSound = true;
				}
			}

			if (sndInfo.nSoundID == INVALID_SOUNDID)
				return;

			if (pSound->GetFlags() & FLAG_SOUND_EVENT)
			{
				pSound->GetInterfaceExtended()->SetSoundPriority( MOVIE_SOUND_PRIORITY );
				pSound->GetInterfaceExtended()->SetVolume(key.fVolume);
			}

			Vec3 SoundPos = pEntity->GetPos();
			Vec3 SoundDir = pEntity->GetWorldTM().TransformVector(FORWARD_DIRECTION);

			Vec3 vOffset(0);
			if (pSound->GetFlags() & FLAG_SOUND_3D)
			{
				bool bVoice = (pSound->GetFlags() & FLAG_SOUND_VOICE) != 0;
				vOffset = Adjust3DSoundOffset(bVoice, pEntity, SoundPos);

				pSound->SetPosition( SoundPos );
				pSound->SetDirection( SoundDir );

				if (key.inRadius > 0 && key.outRadius > 0)
					pSound->GetInterfaceDeprecated()->SetMinMaxDistance( key.inRadius,key.outRadius );
			}
			else
			{
				// 2D sound.
				pSound->GetInterfaceExtended()->SetPan(key.nPan);
			} 

			// start sound with offset to get timing right when QL happened in the middle of a sound key
			int nOffset = (int)((ec.time-key.time)*1000.0f);
			int nLength = pSound->GetLengthMs();

			if (nOffset > 30 && nOffset < nLength) // small 30ms threshold to not skip start
			{
				pSound->GetInterfaceExtended()->SetCurrentSamplePos(nOffset, true);
			}

			if (nLength != 0 && nOffset > nLength && !key.bLoop)
			{
				// If time is outside of sound, do not start it.
				bNewSound = false;
			}

			if (bNewSound)
			{
				((CMovieSystem*)gEnv->pMovieSystem)->OnPlaySound( ec.sequence, pSound );
				if (!pSound->IsPlaying())
				{
					IEntitySoundProxy *pSoundProxy = (IEntitySoundProxy*)pEntity->CreateProxy(ENTITY_PROXY_SOUND);

					if (pSoundProxy)
					{
						if( key.b3DSound )
							pSound->GetInterfaceExtended()->SetFlags( pSound->GetFlags() | FLAG_SOUND_RADIUS );
						else
							pSound->GetInterfaceExtended()->SetFlags( pSound->GetFlags() & ~FLAG_SOUND_RADIUS );

						pSound->GetInterfaceExtended()->SetCurrentSamplePos( (int32)(key.fStartTime*1000.0f), true );

						pSoundProxy->PlaySound(pSound, vOffset, FORWARD_DIRECTION, 1.0f, key.bLipSync);
					}
				}
			}
		}
	}
}

//////////////////////////////////////////////////////////////////////////
void CAnimEntityNode::ReleaseAllAnims()
{
	IEntity *pEntity = GetEntity();
	if (!pEntity)
		return;

	ICharacterInstance *pCharacter = pEntity->GetCharacter(0);
	if (!pCharacter)
		return;
	IAnimationSet* pAnimations = pCharacter->GetIAnimationSet();
	assert(pAnimations);
	for (TStringSetIt It=m_setAnimationSinks.begin();It!=m_setAnimationSinks.end();++It)
	{
		const char *pszName=(*It).c_str();
		//pCharacter->RemoveAnimationEventSink(pszName, this);
		//pAnimations->UnloadAnimation(pszName);
	}
	m_setAnimationSinks.clear();

	if (AnimationPlaying())
	{
		pCharacter->GetISkeletonAnim()->SetTrackViewExclusive(0);

		StopAnimationsAllLayers(pCharacter->GetISkeletonAnim());

		pCharacter->SetAnimationSpeed(1.0000f);
		pCharacter->GetISkeletonAnim()->SetAnimationDrivenMotion(m_bWasTransRot);
		m_layerPlaysAnimation[0] = m_layerPlaysAnimation[1] = m_layerPlaysAnimation[2] = false;

		NotifyEntityScript(pEntity,"OnSequenceAnimationStop");
	}

	ReleaseAllFacialSequences();
}

//////////////////////////////////////////////////////////////////////////
void CAnimEntityNode::OnEndAnimation(const char *sAnimation)
{
	IEntity *pEntity = GetEntity();
	if (!pEntity)
		return;

	TStringSetIt It=m_setAnimationSinks.find(sAnimation);
	if (It==m_setAnimationSinks.end())
		return;	// this anim was not started by us...
	m_setAnimationSinks.erase(It);
	ICharacterInstance *pCharacter = pEntity->GetCharacter(0);
	if (!pCharacter)
		return;

	IAnimationSet* pAnimations = pCharacter->GetIAnimationSet();
	assert(pAnimations);
	//pCharacter->RemoveAnimationEventSink(sAnimation, this);
	//pAnimations->UnloadAnimation(sAnimation);
}

// return active keys ( maximum: 2 in case of overlap ) in the right order
static int32 GetActiveKeys(int32 activeKeys[], f32 ectime, CCharacterTrack* track)
{
	int32 numActiveKeys = 0;

	f32 time = 0.0f;
	bool swap = false;

	int32 numKeys = track->GetNumKeys();
	for (int i = 0; i < numKeys; ++i)
	{
		ICharacterKey key;
		track->GetKey(i, &key);

		if ((key.time <= ectime) && (key.time + track->GetKeyDuration(i) > ectime))
		{
			activeKeys[numActiveKeys] = i;

			// last key had a bigger 
			if (time > key.time)
				swap = true;
			time = key.time;
			numActiveKeys++;

			// not more than 2 concurrent keys allowed
			if (numActiveKeys == 2)
				break;
		}
	}

	if (swap)
	{
		int32 temp = activeKeys[1];
		activeKeys[1] = activeKeys[0];
		activeKeys[0] = temp;
	}

	// use the first if we are before the first key
	if (!numActiveKeys && track->GetNumKeys())
	{
		ICharacterKey key0;
		track->GetKey(0,&key0);
		if (ectime < key0.time)
		{
			numActiveKeys = 1;
			activeKeys[0] = 0;
		}
	}
	return numActiveKeys;
}

static bool GetNearestKeys(int32 activeKeys[], f32 ectime, CCharacterTrack* track, int32& numActiveKeys)
{
	f32 time = 0.0f;

	f32 nearestLeft = 0.0f;
	f32 nearestRight = 10000.0f;

	int32 left = -1, right = -1;

	int32 numKeys = track->GetNumKeys();
	for (int i = 0; i < numKeys; ++i)
	{
		ICharacterKey key;
		track->GetKey(i, &key);

		f32 endTime = key.time + track->GetKeyDuration(i);
		if ((endTime < ectime) && (endTime > nearestLeft))
		{
			left = i;
			nearestLeft = endTime;
		}

		if ((key.time > ectime) && (key.time < nearestRight))
		{
			right = i;
			nearestRight = key.time;
		}
	}

	if (left != -1)
	{
		ICharacterKey possibleKey;
		track->GetKey(left, &possibleKey);

		assert(possibleKey.m_bLoop == false);

		if (!possibleKey.m_bBlendGap)
		{
			// if no blending is set
			// set animation to the last frame of the nearest animation
			// to the left
			activeKeys[0] = left;
			numActiveKeys = 1;
			return false;
		}
	}

	// found gap-blend neighbors
	if ((right != -1) && (left != -1))
	{
		activeKeys[0] = left;
		activeKeys[1] = right;
		return true;
	}
	return false;
}

static bool HaveKeysChanged(int32 activeKeys[], int32 previousKeys[])
{
	return !((activeKeys[0] == previousKeys[0]) && (activeKeys[1] == previousKeys[1]));
}



//////////////////////////////////////////////////////////////////////////
void CAnimEntityNode::AnimateCharacterTrack( class CCharacterTrack* track,SAnimContext &ec,int layer )
{
	ISystem* pISystem = GetISystem();
	IRenderer* pIRenderer			= gEnv->pRenderer;
	IRenderAuxGeom* pAuxGeom	= pIRenderer->GetIRenderAuxGeom();

	IEntity *pEntity = GetEntity();
	if (!pEntity)
		return;

	ICharacterInstance *pCharacter = pEntity->GetCharacter(0);
	if (!pCharacter)
		return;

	ISkeletonAnim* pISkeletonAnim = pCharacter->GetISkeletonAnim();

	track->GetActiveKey(ec.time, 0);	// To sort keys

	int32 activeKeys[2] = {-1, -1};
	int32 numActiveKeys = GetActiveKeys(activeKeys, ec.time, track);

	// decide if blending gap is asked for or possible
	bool blendGap = false;
	if (numActiveKeys == 0)
		blendGap = GetNearestKeys(activeKeys, ec.time, track, numActiveKeys);
	
	uint32 layer012 = LayerToTrack(layer);
	bool bAnyChange = CheckTimeJumpingOrOtherChanges(ec, activeKeys, numActiveKeys, pCharacter, layer);

	// the used keys have changed - be it overlapping, single, nearest or none at all
	if (bAnyChange)
	{
		if (m_bTimeJumped[layer012] == false)	// In case of the time-jumped, the existing animation must not be cleared.
		{
			pISkeletonAnim->ClearFIFOLayer(layer);
		}
		
		m_layerPlaysAnimation[layer012] = false;

		// rebuild the transition queue
		for (int32 i = 0; i < 2; ++i)
		{
			int32 k = activeKeys[i];
			if (k < 0)
				break;

			ICharacterKey key;
			track->GetKey(k, &key);

			float t = ec.time - key.time;
			t = key.m_startTime + t*key.m_speed;

			if (key.m_animation[0])
			{
				// retrieve the animation collection for the model
				IAnimationSet* pAnimations = pCharacter->GetIAnimationSet();
				assert (pAnimations);

				if (key.m_bUnload)
					m_setAnimationSinks.insert(TStringSetIt::value_type(key.m_animation));

				if (pISkeletonAnim->GetAnimationDrivenMotion() && (!AnimationPlaying()))
					m_bWasTransRot = true;

				pISkeletonAnim->SetAnimationDrivenMotion(key.m_bInPlace ? 1 : 0);
				pISkeletonAnim->SetTrackViewExclusive(1);

				// Start playing animation.
				CryCharAnimationParams aparams;
				aparams.m_nFlags = CA_TRACK_VIEW_EXCLUSIVE|CA_ALLOW_ANIM_RESTART;
				if (key.m_bLoop)
					aparams.m_nFlags |= CA_LOOP_ANIMATION;
				aparams.m_nLayerID = layer;
				aparams.m_fTransTime = -1.0f;

				StartAnimation(pISkeletonAnim, key.m_animation, aparams);

				NotifyEntityScript(pEntity,"OnSequenceAnimationStart");

				m_layerPlaysAnimation[layer012] = true;

				// fix duration?
				int animId = pAnimations->GetAnimIDByName(key.m_animation);
				if (animId >= 0)
				{
					//pCharacter->EnableTimeUpdate(false);
					//pCharacter->SetAnimationSpeed(layer, 0.0000f);
					//float duration = pAnimations->GetLength(animId);
					float duration = pAnimations->GetDuration_sec(animId);// + pAnimations->GetStart(animId);
					if (duration != key.m_duration)
					{
						key.m_duration = duration;
						track->SetKey(k, &key);
					}
				}
			}
		}

		m_lastAnimationKeys[layer012][0] = activeKeys[0];
		m_lastAnimationKeys[layer012][1] = activeKeys[1];

		if (!AnimationPlaying())
		{
			// There is no animation left playing - exit TrackViewExclusive mode
			pCharacter->GetISkeletonAnim()->SetTrackViewExclusive(0);
			StopAnimationsAllLayers(pCharacter->GetISkeletonAnim());
			pCharacter->SetAnimationSpeed(1.0000f);
			pCharacter->GetISkeletonAnim()->SetAnimationDrivenMotion(m_bWasTransRot);

			NotifyEntityScript(pEntity,"OnSequenceAnimationStop");
			return;
		}
	}

	if (m_layerPlaysAnimation[layer012])
	{
		if (m_bTimeJumped[layer012])
		{
			assert(numActiveKeys == 1 && activeKeys[0] >= 0);
			UpdateAnimTimeJumped(activeKeys[0], track, ec.time, pCharacter, layer, !bAnyChange);
		}
		// regular one- or two-animation(s) case
		else if (numActiveKeys > 0)
		{
			UpdateAnimRegular(numActiveKeys, activeKeys, track, ec.time, pCharacter, layer, !bAnyChange);
		}
		// blend gap
		else if (blendGap)
		{
			UpdateAnimBlendGap(activeKeys, track, ec.time, pCharacter, layer);
		}
	}
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void CAnimEntityNode::StopExpressions()
{
	if (m_setExpressions.empty())
		return;

	IEntity *pEntity = GetEntity();
	if (!pEntity)
		return;

	/*
	IEntityCharacter *pEntChar=m_entity->GetCharInterface();
	if (!pEntChar)
		return;
	ILipSync *pLipSync=pEntChar->GetLipSyncInterface();
	if (!pLipSync)
		return;
	for (TStringSetIt It=m_setExpressions.begin();It!=m_setExpressions.end();++It)
	{
		pLipSync->StopExpression((*It).c_str());
	}
	*/
	ICharacterInstance* pInst = pEntity->GetCharacter(0);
	if (pInst)
	{
   pInst->GetIMorphing()->StopAllMorphs();
	}

	m_setExpressions.clear();
}

void CAnimEntityNode::AnimateExpressionTrack(CExprTrack *pTrack, SAnimContext &ec)
{
	IEntity *pEntity = GetEntity();
	if (!pEntity)
		return;

	IExprKey Key;
	int nKeys=pTrack->GetNumKeys();
	// we go through all the keys, since expressions may overlap
	for (int nKey=0;nKey<nKeys;nKey++)
	{
		pTrack->GetKey(nKey, &Key);
		if ((!Key.pszName) || (!Key.pszName[0]))
			return;

		float fKeyLentgh=Key.fBlendIn+Key.fHold+Key.fBlendOut;
		float fOffset=ec.time-Key.time;

		CryCharMorphParams MorphParams;
		MorphParams.m_fAmplitude=Key.fAmp;
		MorphParams.m_fBlendIn=Key.fBlendIn;
		MorphParams.m_fBlendOut=Key.fBlendOut;
		MorphParams.m_fLength=Key.fHold;
		MorphParams.m_fStartTime=fOffset;
		//MorphParams.m_nFlags |= MorphParams.FLAGS_RECURSIVE;
		ICharacterInstance* pInst = pEntity->GetCharacter(0);
		if (pInst)
		{
	//		pInst->StartMorph (Key.pszName, MorphParams);

	//	bool CLipSync::DoExpression(const char* pszMorphTarget, CryCharMorphParams &MorphParams, bool bAnim)
	//	{
	//		if (!m_pCharInst)
	//			return false; // not initialized
	//		ICryCharModel *pModel=m_pCharInst->GetModel();
	//		ASSERT(pModel);
	//		IAnimationSet *pAnimSet=pModel->GetAnimationSet();
	//		ASSERT(pAnimSet);
	//		int nMorphTargetId=pAnimSet->FindMorphTarget(pszMorphTarget);
	//		if (nMorphTargetId==-1)
	//			return false;	// no such morph-target
			// try to set time first in case it is already playing
			//	if (!m_pCharInst->SetMorphTime(nMorphTargetId, MorphParams.fStartTime))

			if ((Key.time>ec.time) || (fOffset>=fKeyLentgh))
			{
				continue;
			}
			
			IMorphing* pIMorphing = pInst->GetIMorphing();
			pIMorphing->StopMorph(Key.pszName);
			pIMorphing->StartMorph(Key.pszName, MorphParams);
			pIMorphing->SetMorphSpeed(Key.pszName, 0.0f);
		}

		/*
		IEntityCharacter *pEntChar=m_entity->GetCharInterface();
		if (!pEntChar)
			return;
		ILipSync *pLipSync=pEntChar->GetLipSyncInterface();
		if (pLipSync)
		{
			float fKeyLentgh=Key.fBlendIn+Key.fHold+Key.fBlendOut;
			float fOffset=ec.time-Key.time;
			if ((Key.time>ec.time) || (fOffset>=fKeyLentgh))
			{
//				pLipSync->StopExpression(Key.pszName);
				continue;
			}
			CryCharMorphParams MorphParams;
			MorphParams.fAmplitude=Key.fAmp;
			MorphParams.fBlendIn=Key.fBlendIn;
			MorphParams.fBlendOut=Key.fBlendOut;
			MorphParams.fLength=Key.fHold;
			MorphParams.fStartTime=fOffset;
			if (pLipSync->DoExpression(Key.pszName, MorphParams, false))
				m_setExpressions.insert(Key.pszName);
		}
		*/
	}
}

//////////////////////////////////////////////////////////////////////////
void CAnimEntityNode::AnimateFacialSequence( CFaceSeqTrack *pTrack, SAnimContext &ec )
{
	IEntity *pEntity = GetEntity();
	if (!pEntity)
		return;

	ICharacterInstance *pCharacter = pEntity->GetCharacter(0);
	if (!pCharacter)
		return;

	IFacialInstance *pFaceInstance = pCharacter->GetFacialInstance();
	if (!pFaceInstance)
		return;

	IFaceSeqKey key;
	int nkey = pTrack->GetActiveKey(ec.time,&key);
	if (nkey >= 0 && nkey < pTrack->GetNumKeys())
	{
		_smart_ptr<IFacialAnimSequence> pSeq = LoadFacialSequence(key.szSelection);
		if (pSeq)
		{
			key.fDuration = pSeq->GetTimeRange().Length();
			pTrack->SetKey(nkey, &key);

			float t = ec.time - key.time;
			if (t <= key.fDuration)
			{
				if (!pFaceInstance->IsPlaySequence(pSeq, eFacialSequenceLayer_Trackview))
				{
					pFaceInstance->PlaySequence(pSeq, eFacialSequenceLayer_Trackview);
					pFaceInstance->PauseSequence(eFacialSequenceLayer_Trackview, true);
				}
				pFaceInstance->SeekSequence(eFacialSequenceLayer_Trackview,t);
			}
			else
			{
				if (pFaceInstance->IsPlaySequence(pSeq, eFacialSequenceLayer_Trackview))
					pFaceInstance->StopSequence(eFacialSequenceLayer_Trackview);
			}
		}
	}
}

//////////////////////////////////////////////////////////////////////////
void CAnimEntityNode::AnimateLookAt( CLookAtTrack *pTrack, SAnimContext &ec )
{
		IEntity *pEntity = GetEntity();
		if (!pEntity)
				return;

		ICharacterInstance *pCharacter = 0;

		IAIActor* pAIActor = CastToIAIActorSafe(pEntity->GetAI());
		if (!pAIActor)
				pCharacter = pEntity->GetCharacter(0);

		EntityId lookAtEntityId = 0;
		bool allowAdditionalTransforms = 0;
		ELookAtKeyBoneSet boneSet = eLookAtKeyBoneSet_HeadEyes;

		ILookAtKey key;
		int nkey = pTrack->GetActiveKey(ec.time,&key);
		allowAdditionalTransforms = true;
		boneSet = eLookAtKeyBoneSet_HeadEyes;
		if (nkey >= 0)
		{
				allowAdditionalTransforms = key.bAllowAdditionalTransforms;
				boneSet = key.boneSet;
				if ((m_lookAtTarget[0] && !m_lookAtEntityId) || strcmp(key.szSelection,m_lookAtTarget) != 0)
				{
						m_lookAtTarget = key.szSelection;
						IEntity *pTargetEntity = NULL;
						if (strcmp(key.szSelection, "_LocalPlayer") != 0)
								pTargetEntity = gEnv->pEntitySystem->FindEntityByName(key.szSelection);
						else if (gEnv->pGame)
						{
								IActor* pLocalActor = gEnv->pGame->GetIGameFramework()->GetClientActor();
								if (pLocalActor)
										pTargetEntity = pLocalActor->GetEntity();
						}
						if (pTargetEntity)
						{
								lookAtEntityId = pTargetEntity->GetId();
						}
						else
								lookAtEntityId = 0;
				}
				else
				{
						lookAtEntityId = m_lookAtEntityId;
				}
		}
		else
		{
				lookAtEntityId = 0;
				m_lookAtTarget = "";
				allowAdditionalTransforms = true;
				boneSet = eLookAtKeyBoneSet_HeadEyes;
		}

		if (m_lookAtEntityId != lookAtEntityId 
				|| m_allowAdditionalTransforms != allowAdditionalTransforms 
				|| m_boneSet != boneSet)
		{
				m_lookAtEntityId = lookAtEntityId;
				m_allowAdditionalTransforms = allowAdditionalTransforms;
				m_boneSet = boneSet;

				// We need to enable smoothing for the facial animations, since look ik can override them and cause snapping.
				IFacialInstance* pFacialInstance = (pCharacter ? pCharacter->GetFacialInstance() : 0);
				if (pFacialInstance)
						pFacialInstance->TemporarilyEnableBoneRotationSmoothing();
		}

		IEntity *pLookAtEntity = 0;
		if (m_lookAtEntityId)
				pLookAtEntity = gEnv->pEntitySystem->GetEntity(m_lookAtEntityId);

		if (pLookAtEntity)
		{
				Vec3 pos = pLookAtEntity->GetWorldPos();
				ICharacterInstance *pLookAtChar = pLookAtEntity->GetCharacter(0);
				if (pLookAtChar)
				{
						// Try look at head bone.
						int16 nHeadBoneId = pLookAtChar->GetISkeletonPose()->GetJointIDByName( HEAD_BONE_NAME );
						if (nHeadBoneId >= 0)
						{
								pos = pLookAtEntity->GetWorldTM().TransformPoint( pLookAtChar->GetISkeletonPose()->GetAbsJointByID(nHeadBoneId).t );
								//	gEnv->pRenderer->GetIRenderAuxGeom()->DrawSphere( pos,0.2f,ColorB(255,0,0,255) );
						}
				}
				float blends[5];
				SelectLookIKBlends(m_boneSet, blends);
				if (pCharacter)
						pCharacter->GetISkeletonPose()->SetLookIK( true,DEG2RAD(120),pos,blends,m_allowAdditionalTransforms );
				if (pAIActor)
						pAIActor->SetLookAtPointPos(pos);
		}
		else
		{
				if (pCharacter)
						pCharacter->GetISkeletonPose()->SetLookIK( false,false,Vec3(0,0,0) );
				if (pAIActor)
						pAIActor->ResetLookAt();
		}
}

//////////////////////////////////////////////////////////////////////////
void CAnimEntityNode::SelectLookIKBlends(ELookAtKeyBoneSet boneSet, float* blends)
{
		switch (boneSet)
		{
		case eLookAtKeyBoneSet_Eyes:
				blends[0] = 0.0f;
				blends[1] = 0.0f;
				blends[2] = 0.0f;
				blends[3] = 0.0f;
				blends[4] = 0.0f;
				break;

		case eLookAtKeyBoneSet_SpineHeadEyes:
				blends[0] = 0.04f;
				blends[1] = 0.06f;
				blends[2] = 0.08f;
				blends[3] = 0.15f;
				blends[4] = 0.60f;
				break;

		case eLookAtKeyBoneSet_HeadEyes:
		default:
				blends[0] = 0.0f;
				blends[1] = 0.0f;
				blends[2] = 0.0f;
				blends[3] = 0.0f;
				blends[4] = 0.60f;
				break;
		}
}

//////////////////////////////////////////////////////////////////////////
void CAnimEntityNode::Serialize( XmlNodeRef &xmlNode,bool bLoading,bool bLoadEmptyTracks )
{
	CAnimNode::Serialize( xmlNode,bLoading,bLoadEmptyTracks );
	if (bLoading)
	{
		xmlNode->getAttr( "Pos",m_pos );
		xmlNode->getAttr( "Rotate",m_rotate );
		xmlNode->getAttr( "Scale",m_scale );
		xmlNode->getAttr( "EntityGUID",m_entityGuid );
		
		xmlNode->getAttr( "EntityGUIDTarget",m_entityGuidTarget );
		xmlNode->getAttr( "EntityGUIDSource",m_entityGuidSource );

		bool bBoneLinkTransformEnabled = false;
		xmlNode->getAttr( "BoneLinkTransformEnabled",bBoneLinkTransformEnabled );
		EnableBoneLinkTransform(bBoneLinkTransformEnabled);
	}
	else
	{
		xmlNode->setAttr( "Pos",m_pos );
		xmlNode->setAttr( "Rotate",m_rotate );
		xmlNode->setAttr( "Scale",m_scale );

		xmlNode->setAttr( "EntityGUID",m_entityGuid );

		if(m_entityGuidTarget)
			xmlNode->setAttr( "EntityGUIDTarget",m_entityGuidTarget );
		if(m_entityGuidSource)
			xmlNode->setAttr( "EntityGUIDSource",m_entityGuidSource );

		xmlNode->setAttr( "BoneLinkTransformEnabled",IsBoneLinkTransformEnabled() );
	}
}

//////////////////////////////////////////////////////////////////////////
void CAnimEntityNode::OffsetPosTrack( const Vec3 &offset )
{

}

//////////////////////////////////////////////////////////////////////////
XmlNodeRef CAnimEntityNode::SaveToColladaInFixedFPS(float fps, const ENodeExportType exportType, const IAnimSequence* seq, const IEntity* ent)const
{
	
	XmlNodeRef colladaNode = CAnimNode::SaveToColladaInFixedFPS(fps, exportType, seq, ent);

	// <library_visual_scenes>
	XmlNodeRef libraryVisualScenesNode = colladaNode->newChild("library_visual_scenes");
		XmlNodeRef visualSceneNode = libraryVisualScenesNode->newChild("visual_scene");
		visualSceneNode->setAttr("id", "RootNode");
		visualSceneNode->setAttr("name", "RootNode");
			XmlNodeRef node = visualSceneNode->newChild("node");
			node->setAttr("id", m_name);
			node->setAttr("name", m_name);
				XmlNodeRef translateNode = node->newChild("translate");
				translateNode->setAttr("sid", "translate");
				translateNode->setContent("0 0 0");
				XmlNodeRef rotateNode = node->newChild("rotate");
				rotateNode->setAttr("sid", "rotateZ");
				rotateNode->setContent("0 0 1 0");
				rotateNode = node->newChild("rotate");
				rotateNode->setAttr("sid", "rotateY");
				rotateNode->setContent("0 1 0 0");
				rotateNode = node->newChild("rotate");
				rotateNode->setAttr("sid", "rotateX");
				rotateNode->setContent("1 0 0 0");

	// <scene>
	XmlNodeRef sceneNode = colladaNode->newChild("scene");
		sceneNode->newChild("instance_visual_scene")->setAttr("url", "#RootNode");
	
	return colladaNode;
}

//////////////////////////////////////////////////////////////////////////
bool CAnimEntityNode::LoadFromCollada(XmlNodeRef colladaNode)
{
	if (CAnimNode::LoadFromCollada(colladaNode) == false)
		return false;

	// Further sanity checks
	XmlNodeRef libraryVisualScenesNode = colladaNode->findChild("library_visual_scenes");
	if (!libraryVisualScenesNode)
		return false;
	XmlNodeRef visualSceneNode = libraryVisualScenesNode->findChild("visual_scene");
	if (!visualSceneNode)
		return false;
	XmlString rootID;
	if (visualSceneNode->getAttr("id", rootID) == false)
		return false;
	XmlNodeRef sceneNode = colladaNode->findChild("scene");
	if (!sceneNode)
		return false;
	XmlString sceneUrl;
	if (sceneNode->findChild("instance_visual_scene")->getAttr("url", sceneUrl) == false)
		return false;
	rootID = ("#" + rootID).c_str();
	if (rootID != sceneUrl)
		return false;

	// Get the node name.
	XmlNodeRef node = visualSceneNode->findChild("node");
	if (!node)
		return false;
	XmlString nodeID;
	node->getAttr("id", nodeID);

	// Removes pos, rot tracks if they exist already.
	IAnimTrack *pPosTrack = NULL, *pRotTrack = NULL;
	if (pPosTrack = GetTrackForParameter(APARAM_POS))
		RemoveTrack(pPosTrack);
	if (pRotTrack = GetTrackForParameter(APARAM_ROT))
		RemoveTrack(pRotTrack);
	pPosTrack = CreateTrack(APARAM_POS);
	pRotTrack = CreateTrack(APARAM_ROT);

	// Find the animation data which are channeled to the node.
	XmlNodeRef libraryAnimationsNode = colladaNode->findChild("library_animations");
	if (!libraryAnimationsNode)
		return false;
	XmlNodeRef parentNode = libraryAnimationsNode;
	// It can be the case of animations right under the library tag,
	// or the case of animations under another parent animation tag inside the library tag.
	if (libraryAnimationsNode->getChildCount() == 1
	&& libraryAnimationsNode->getChild(0)->findChild("channel") == 0)
		parentNode = libraryAnimationsNode->getChild(0);
	nodeID += "/";
	for (int i=0; i<parentNode->getChildCount(); ++i)
	{
		XmlNodeRef animationNode = parentNode->getChild(i);
		XmlNodeRef channelNode = animationNode->findChild("channel");
		if (!channelNode)
			continue;
		XmlString channelTarget;
		if (channelNode->getAttr("target", channelTarget) == false)
			continue;
		if (channelTarget.substr(0, nodeID.length()) != nodeID)
			continue;
		// Now we've found a valid animation data for this node.
		XmlString whichChannel(channelTarget.substr(nodeID.length()));
		if (whichChannel == "translate.X")
		{
			pPosTrack->GetSubTrack(0)->LoadFromCollada(animationNode);
		}
		else if (whichChannel == "translate.Y")
		{
			pPosTrack->GetSubTrack(1)->LoadFromCollada(animationNode);
		}
		else if (whichChannel == "translate.Z")
		{
			pPosTrack->GetSubTrack(2)->LoadFromCollada(animationNode);
		}
		else if (whichChannel == "rotateX.ANGLE")
		{
			pRotTrack->GetSubTrack(0)->LoadFromCollada(animationNode);
		}
		else if (whichChannel == "rotateY.ANGLE")
		{
			pRotTrack->GetSubTrack(1)->LoadFromCollada(animationNode);
		}
		else if (whichChannel == "rotateZ.ANGLE")
		{
			pRotTrack->GetSubTrack(2)->LoadFromCollada(animationNode);
		}
	}

	return true;
}

//////////////////////////////////////////////////////////////////////////
void CAnimEntityNode::EnableBoneLinkTransform(bool enable)
{
	if (enable)
	{
		IEntity *pEntity = GetEntity();
		assert(pEntity);
		if (pEntity)
		{
			IEntityLink *pLink = pEntity->GetEntityLinks();
			while (pLink)
			{
				if (IEntityLink::IsBoneAttachLinkName(pLink->name))
				{
					break;
				}
				pLink = pLink->next;
			}
			assert(pLink);
			if (pLink)
			{
				m_boneLinkName = pLink->name;
				return;
			}
		}
	}

	m_boneLinkName = "";
}

//////////////////////////////////////////////////////////////////////////
bool CAnimEntityNode::IsBoneLinkTransformEnabled() const
{
	if (m_boneLinkName.empty())
		return false;
	else
		return true;
}

//////////////////////////////////////////////////////////////////////////
void CAnimEntityNode::UpdateBoneLink( const Vec3 *pPos, const Quat *pRot)
{
	IEntity *pEntity = GetEntity();
	if (pEntity == NULL)
		return;

	// Get the entity link by name.
	IEntityLink *pLink = pEntity->GetEntityLinks();
	while (pLink)
	{
		if (m_boneLinkName == pLink->name)
		{
			break;
		}
		pLink = pLink->next;
	}
	if (pLink == NULL)
		return;

	// Get the target entity to which this entity is bone-attached.
	IEntity *pTargetEntity = gEnv->pEntitySystem->GetEntity(pLink->entityId);
	if (pTargetEntity == NULL)
		return;

	ICharacterInstance *pCharacter = pTargetEntity->GetCharacter(0);
	if (pCharacter == NULL)
		return;
	ISkeletonPose *pSkeletonPose = pCharacter->GetISkeletonPose();
	if (pSkeletonPose == NULL)
		return;

	// Update the relative transformation info in the attachment.
	IAttachmentManager *pAttachmentManager = pCharacter->GetIAttachmentManager();
	string attachmentName = pLink->name;
	attachmentName += "-";
	attachmentName += pEntity->GetName();
	IAttachment *pAttachment = pAttachmentManager->GetInterfaceByName(attachmentName);

	if (pAttachment == NULL)
		return;
	QuatT relXform = pAttachment->GetAttRelativeDefault();
	if (pPos)
		relXform.t = *pPos;
	if (pRot)
		relXform.q = *pRot;
	pAttachment->SetAttRelativeDefault(relXform);
}

void CAnimEntityNode::ApplyAnimKey( int32 keyIndex, class CCharacterTrack* track, float ectime, 
																		ICharacterInstance * pCharacter, int layer, int animIndex, bool bAnimEvents )
{
	ISkeletonAnim* pISkeletonAnim = pCharacter->GetISkeletonAnim();

	ICharacterKey key;
	track->GetKey(keyIndex, &key);

	float t = ectime - key.time;
	t = key.m_startTime + t * key.m_speed;

	if ((key.GetValidEndTime() - key.m_startTime) > 0.0f
		&& key.m_duration > 0.0f)
	{
		if (t < key.m_startTime)
		{
			t = key.m_startTime;
		}
		else if (key.m_bLoop && key.m_duration > 0.0f)
		{
			t = fmod(t, key.m_duration);
		}
		else if (t > key.GetValidEndTime())
		{
			t = key.GetValidEndTime();
		}
		pCharacter->SetAnimationSpeed(0.0000f);
		f32 fNormalizedTime = t / key.m_duration;
		assert(fNormalizedTime >= 0.0f && fNormalizedTime <= 1.0f);
		ManualSeekAnimationInFIFO(pISkeletonAnim, layer, animIndex, fNormalizedTime, bAnimEvents);
		SetLayerTime(pISkeletonAnim, layer,fNormalizedTime);
	}
}

void CAnimEntityNode::UpdateAnimTimeJumped( int32 keyIndex, class CCharacterTrack* track, float ectime, ICharacterInstance * pCharacter, int layer, bool bAnimEvents )
{
	f32 blendWeight = 0.0f;
	ISkeletonAnim* pISkeletonAnim = pCharacter->GetISkeletonAnim();
	if (pISkeletonAnim->GetNumAnimsInFIFO(layer) == 2)
	{
		ICharacterKey key;
		track->GetKey(keyIndex, &key);

		float transitionTime = clamp_tpl(track->GetKeyDuration(keyIndex), FLT_EPSILON, TIMEJUMPED_TRANSITION_TIME);
		blendWeight = (ectime - m_jumpTime[LayerToTrack(layer)]) / transitionTime;
		assert(0 <= blendWeight && blendWeight <= 1.0f);
		if (blendWeight > 1.0f)
			blendWeight = 1.0f;
		else if (blendWeight < 0)
			blendWeight = 0;

		SetTrackViewMixingWeight(pISkeletonAnim, layer, blendWeight);

		ApplyAnimKey(keyIndex, track, ectime, pCharacter, layer, 1, bAnimEvents);
	}
	else
	{
		SetTrackViewMixingWeight(pISkeletonAnim, layer, 0);
	}
}

void CAnimEntityNode::UpdateAnimRegular( int32 numActiveKeys, int32 activeKeys[], class CCharacterTrack* track, float ectime, ICharacterInstance * pCharacter, int layer, bool bAnimEvents )
{
	ISkeletonAnim* pISkeletonAnim = pCharacter->GetISkeletonAnim();
	f32 blendWeight = 0.0f;
	// cross-fade
	if (numActiveKeys == 2)
	{
		ICharacterKey key1, key2;
		track->GetKey(activeKeys[0], &key1);

		track->GetKey(activeKeys[1], &key2);

		f32 key1EndTime = key1.time + track->GetKeyDuration(activeKeys[0]);
		f32 t0 = key1EndTime - key2.time;
		f32 t = ectime - key2.time;
		if (key1EndTime == key2.time)
			blendWeight = 1.0f;
		else
			blendWeight = t / t0;

		assert(0 <= blendWeight && blendWeight <= 1.0f);
		SetTrackViewMixingWeight(pISkeletonAnim, layer, blendWeight);
	}

	for (int32 i = 0; i < 2; ++i)
	{
		if (activeKeys[i] < 0)
			break;
		ApplyAnimKey(activeKeys[i], track, ectime, pCharacter, layer, i, bAnimEvents);
	}
}

void CAnimEntityNode::UpdateAnimBlendGap( int32 activeKeys[], class CCharacterTrack* track, float ectime, ICharacterInstance * pCharacter, int layer )
{
	f32 blendWeight = 0.0f;
	ISkeletonAnim* pISkeletonAnim = pCharacter->GetISkeletonAnim();
	ICharacterKey key1, key2;
	track->GetKey(activeKeys[0], &key1);
	track->GetKey(activeKeys[1], &key2);

	f32 key1EndTime = key1.time + track->GetKeyDuration(activeKeys[0]);
	f32 t0 = key2.time - key1EndTime;
	f32 t = ectime - key1EndTime;
	if (key1EndTime == key2.time)
		blendWeight = 1.0f;
	else
		blendWeight = t / t0;

	pCharacter->SetAnimationSpeed(0.0000f);
	f32 endTimeNorm = 1.0f;
	if (key1.m_duration > 0)
		endTimeNorm = key1.GetValidEndTime() / key1.m_duration;
	assert(endTimeNorm >= 0.0f && endTimeNorm <= 1.0f);
	ManualSeekAnimationInFIFO(pISkeletonAnim, layer, 0, endTimeNorm, false);
	f32 startTimeNorm = 0.0f;
	if (key2.m_duration)
		startTimeNorm = key2.m_startTime / key2.m_duration;
	assert(startTimeNorm >= 0.0f && startTimeNorm <= 1.0f);
	ManualSeekAnimationInFIFO(pISkeletonAnim, layer, 1, startTimeNorm, false);
	SetTrackViewMixingWeight(pISkeletonAnim, layer, blendWeight);
}

bool CAnimEntityNode::CheckTimeJumpingOrOtherChanges( const SAnimContext &ec, 
																									 int32 activeKeys[], int32 numActiveKeys, 
																									 ICharacterInstance * pCharacter, int layer )
{
	ISkeletonAnim* pISkeletonAnim = pCharacter->GetISkeletonAnim();
	bool bEditing = gEnv->IsEditor() && gEnv->IsEditorGameMode() == false;
	bool bJustJumped = ec.bSingleFrame && bEditing == false;
	uint32 layer012 = LayerToTrack(layer);
	if (bJustJumped)
	{
		m_jumpTime[layer012] = ec.time;
	}
	bool bKeysChanged = HaveKeysChanged(activeKeys, m_lastAnimationKeys[layer012]);
	bool bAnyChange = bKeysChanged || bJustJumped;
	if (m_bTimeJumped[layer012])
	{
		const bool bJumpBlendingDone = ec.time - m_jumpTime[layer012] > TIMEJUMPED_TRANSITION_TIME;
		bAnyChange = bAnyChange || bJumpBlendingDone;
		if (bAnyChange)
		{
			m_bTimeJumped[layer012] = false;
			m_jumpTime[layer012] = 0;
		}
	}
	else if (m_bTimeJumped[layer012] == false)
	{
		m_bTimeJumped[layer012] = bJustJumped;
		if (m_bTimeJumped[layer012])
		{
			if (numActiveKeys != 1)	
			{
				// The transition blending in a time-jumped case only makes sense 
				// when there is a single active animation at that point of time.
				m_bTimeJumped[layer012] = false;
			}
			if (pISkeletonAnim->GetNumAnimsInFIFOAndDeferredQueue(layer) != 1)
			{
				// The transition blending in a time-jumped case only makes sense
				// where there has been a single animation going on for the character.
				m_bTimeJumped[layer012] = false;
			}
		}
	}

	return bAnyChange;
}

void CAnimEntityNode::PrecacheKeys(float time)
{
	// Update durations of all character animations.
	IEntity *pEntity = GetEntity();
	if (!pEntity)
		return;
	ICharacterInstance *pCharacter = pEntity->GetCharacter(0);
	if (!pCharacter)
		return;

	ISkeletonAnim* pISkeletonAnim = pCharacter->GetISkeletonAnim();
	if (!pISkeletonAnim)
		return;

	IAnimationSet* pAnimations = pCharacter->GetIAnimationSet();
	if (!pAnimations)
		return;

	int trackCount = NumTracks();
	for (int paramIndex = 0; paramIndex < trackCount; paramIndex++)
	{
		int trackType = m_tracks[paramIndex].paramId;
		IAnimTrack *pTrack = m_tracks[paramIndex].track;

		switch (trackType)
		{
		case APARAM_ANIMATION:
			{
				int numKeys = pTrack->GetNumKeys();
				for (int i = 0; i < numKeys; i++)
				{
					ICharacterKey key;
					pTrack->GetKey( i,&key );

					// always make sure that all animation keys in the time interval
					// [time ; time + ANIMATION_KEY_PRELOAD_INTERVAL] 
					// have an extra reference so they are kept in memory
					if (key.time < time)
					{
						int32 animID = pAnimations->GetAnimIDByName(key.m_animation);
						if (animID >= 0)
						{
							uint32 animPathCRC = pAnimations->GetFilePathCRCByAnimID(animID);					
							m_animationCacher.RemoveFromCache(animPathCRC);
						}
						continue;
					}
					if (key.time < time + ANIMATION_KEY_PRELOAD_INTERVAL)
					{
						int animId = pAnimations->GetAnimIDByName( key.m_animation );
						if (animId >= 0)
						{
							uint32 animPathCRC = pAnimations->GetFilePathCRCByAnimID(animId);
							m_animationCacher.AddToCache(animPathCRC);
						}
						continue;
					}
				}
			}
			break;
		}
	}
}

//////////////////////////////////////////////////////////////////////////
Vec3 CAnimEntityNode::Noise::Get(float time) const
{
	Vec3 noise;

	float phase = time*freq;

	Vec3 phase0 
		= Vec3(15.0f*freq, 55.1f*freq, 101.2f*freq);

	noise.x = gEnv->pSystem->GetNoiseGen()->Noise1D(phase + phase0.x)*amp;
	noise.y = gEnv->pSystem->GetNoiseGen()->Noise1D(phase + phase0.y)*amp;
	noise.z = gEnv->pSystem->GetNoiseGen()->Noise1D(phase + phase0.z)*amp;

	return noise;
}

void CAnimEntityNode::StopAnimationsAllLayers(ISkeletonAnim *pSkeletonAnim)
{
	if (gEnv->IsEditor())
		pSkeletonAnim->StopAnimationsAllLayers();
	else
		GetCMovieSystem()->QueueStopAnimationsAllLayers(pSkeletonAnim);
}

void CAnimEntityNode::StartAnimation(ISkeletonAnim *pSkeletonAnim, const char *animName, const CryCharAnimationParams& animParams)
{
	if (gEnv->IsEditor())
		pSkeletonAnim->StartAnimation(animName, animParams);
	else
		GetCMovieSystem()->QueueStartAnimation(pSkeletonAnim, animName, animParams);
}

void CAnimEntityNode::ManualSeekAnimationInFIFO(ISkeletonAnim *pSkeletonAnim, uint32 layer, uint32 animIndex, float time, bool bAnimEvents)
{
	if (gEnv->IsEditor())
		pSkeletonAnim->ManualSeekAnimationInFIFO(layer, animIndex, time, bAnimEvents);
	else
		GetCMovieSystem()->QueueManualSeekAnimationInFIFO(pSkeletonAnim, layer,animIndex,time, bAnimEvents);
}

void CAnimEntityNode::SetLayerTime(ISkeletonAnim *pSkeletonAnim, uint32 layer, float time)
{
	if (gEnv->IsEditor())
		pSkeletonAnim->SetLayerTime(layer,time);
	else
		GetCMovieSystem()->QueueSetLayerTime(pSkeletonAnim, layer,time);
}

void CAnimEntityNode::SetTrackViewMixingWeight(ISkeletonAnim *pSkeletonAnim, uint32 layer, float weight)
{
	if (gEnv->IsEditor())
		pSkeletonAnim->SetTrackViewMixingWeight(layer,weight);
	else
		GetCMovieSystem()->QueueSetTrackViewMixingWeight(pSkeletonAnim, layer,weight);
}

Vec3 CAnimEntityNode::Adjust3DSoundOffset(bool bVoice, IEntity *pEntity, Vec3& oSoundPos) const
{
	ICharacterInstance * pCharacter = pEntity->GetCharacter(0);
	Vec3 offset(0);
	if (pCharacter)
	{
		oSoundPos = pEntity->GetWorldTM()*pCharacter->GetAABB().GetCenter();
		if (bVoice == false)
		{
			offset = pCharacter->GetAABB().GetCenter();
		}
	}

	return offset; 
}

#undef s_nodeParamsInitialized
#undef s_nodeParams
#undef AddSupportedParam

void CAnimationCacher::AddToCache( uint32 animPathCRC )
{
	uint32 numAnims = m_cachedAnims.size();
	for (uint32 i = 0; i < numAnims; ++i)
	{
		if (m_cachedAnims[i] == animPathCRC)
			return;
	}

	gEnv->pCharacterManager->CAF_AddRef(animPathCRC);
	m_cachedAnims.push_back(animPathCRC);
}

void CAnimationCacher::RemoveFromCache( uint32 animPathCRC )
{
	uint32 numAnims = m_cachedAnims.size();
	for (uint32 i = 0; i < numAnims; ++i)
	{
		if (m_cachedAnims[i] == animPathCRC)
		{
			gEnv->pCharacterManager->CAF_Release(animPathCRC);
			for (uint32 j = i; j < numAnims-1; ++j)
				m_cachedAnims[j] = m_cachedAnims[j+1];
			m_cachedAnims.resize(numAnims-1);
			return;
		}
	}
}

void CAnimationCacher::ClearCache()
{
	uint32 numAnims = m_cachedAnims.size();
	for (uint32 i = 0; i < numAnims; ++i)
	{
		gEnv->pCharacterManager->CAF_Release(m_cachedAnims[i]);
	}
	m_cachedAnims.resize(0);
}
